/******************************************************************************
* 	Copyright (c) 2007-2008 All rights reserved
*		Asemantics S.r.l
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer. 
*
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in
*    the documentation and/or other materials provided with the
*    distribution.
*
* 3. The end-user documentation included with the redistribution,
*    if any, must include the following acknowledgment:
*       "This product includes software developed by 
*	 Asemantics S.r.l."
*    Alternately, this acknowledgment may appear in the software itself,
*    if and wherever such third-party acknowledgments normally appear.
*
* 4. All advertising materials mentioning features or use of this software
*    must display the following acknowledgement:
*    This product includes software developed by Asemantics S.r.l.
*    the Semantic Web company, Rome, London, Leiden and its contributors. 
*
* 5. Neither the name of the company nor the names of its contributors
*    may be used to endorse or promote products derived from this software
*    without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Author: Andrea Marchesini - baku@asemantics.com
*
******************************************************************************/

#include <controller.h>
#include <configure.h>
#include <iostring.h>
#include <process.h>

static gboolean process_parser_single (TBGlobal * data, nxml_t * nxml,
				       nxml_data_t * cur, GList ** list,
				       TBProcess * parent, gboolean is_ref,
				       GError ** error);
static gint process_parser_order (TBSubProcess * a, TBSubProcess * b);
static gboolean process_parser_scheduled (TBProcess * process, gchar * string,
					  GError ** error);

/* PARSER ******************************************************************/
gboolean
process_parser (TBGlobal * data, nxml_t * nxml, GList ** processes,
		gboolean is_ref, GError ** error)
{
  GList *list;
  nxml_data_t *cur;

  nxml_root_element (nxml, &cur);

  for (list = NULL, cur = cur->children; cur; cur = cur->next)
    if (cur->type == NXML_TYPE_ELEMENT
	&& !strcmp (cur->value, TB_CONTROLLER_PROCESSES_TAG))
      {
	for (cur = cur->children; cur; cur = cur->next)
	  if (cur->type == NXML_TYPE_ELEMENT
	      && (!strcmp (cur->value, TB_CONTROLLER_PROCESS_TAG)
		  || !strcmp (cur->value, TB_CONTROLLER_METAPROCESS_TAG))
	      && process_parser_single (data, nxml, cur, &list, NULL, is_ref,
					error) == FALSE)
	    {
	      if (list)
		{
		  g_list_foreach (list, (GFunc) process_free, NULL);
		  g_list_free (list);
		}

	      return FALSE;
	    }
	break;
      }

  *processes = list;
  return TRUE;
}

static gboolean
process_parser_single (TBGlobal * data, nxml_t * nxml, nxml_data_t * cur,
		       GList ** list, TBProcess * parent, gboolean is_ref,
		       GError ** error)
{
  gboolean meta;
  gchar *tmp, *id;
  TBProcess *process;
  nxml_attr_t *attribute;

  TBSubProcess *sub = NULL;

  /* Is it a meta process? */
  if (!strcmp (cur->value, TB_CONTROLLER_METAPROCESS_TAG))
    meta = TRUE;
  else
    meta = FALSE;

  /* Search the ID: */
  for (id = NULL, attribute = cur->attributes; attribute;
       attribute = attribute->next)
    {
      if (!strcmp (attribute->name, TB_CONTROLLER_ID_ATTR))
	{
	  id = attribute->value;
	  break;
	}
    }

  if (!id)
    {
      g_set_error (error, controller_error_quark (), 0,
		   "There is a process without 'id' attribute");
      return FALSE;
    }

  /* Add this process to the parent: */
  if (parent)
    {
      sub = g_malloc0 (sizeof (TBSubProcess));
      sub->process = g_strdup (id);

      parent->processes = g_list_append (parent->processes, sub);
    }

  /* A process without description: */
  if (!cur->children)
    {
      if (!parent)
	{
	  g_set_error (error, controller_error_quark (), 0,
		       "The process '%s' has not a description and not parent",
		       id);
	  return FALSE;
	}

      return TRUE;
    }

  process = g_malloc0 (sizeof (TBProcess));
  process->is_ref = is_ref;

  *list = g_list_prepend (*list, process);

  process->id = g_strdup (id);
  process->global = parent ? FALSE : TRUE;
  process->meta = meta;

  /* Attributes: */
  for (attribute = cur->attributes; attribute; attribute = attribute->next)
    {
      if (!strcmp (attribute->name, TB_CONTROLLER_MULTIPLE_ATTR)
	  && !strcmp (attribute->value, "true"))
	process->multiple = TRUE;

      else if (!strcmp (attribute->name, TB_CONTROLLER_HIDDEN_ATTR)
	       && !strcmp (attribute->value, "true"))
	process->hidden = TRUE;

      else if (!strcmp (attribute->name, TB_CONTROLLER_ISTANCE_ATTR))
	process->istances = atoi (attribute->value);

      else if (!strcmp (attribute->name, TB_CONTROLLER_ORDER_ATTR))
	{
	  if (parent && sub)
	    sub->order = atoi (attribute->value);

	  else
	    {
	      g_set_error (error, controller_error_quark (), 0,
			   "The process '%s' can't have a order value because it is global",
			   process->id);
	      return FALSE;
	    }
	}
    }

  /* Sub tags: */
  for (cur = cur->children; cur; cur = cur->next)
    {
      if (cur->type == NXML_TYPE_ELEMENT)
	{
	  if (!strcmp (cur->value, TB_CONTROLLER_COMMAND_TAG))
	    {
	      if (nxml_get_string (cur, &tmp) == NXML_OK && tmp)
		{
		  process->command = g_strdup (tmp);
		  free (tmp);
		}

	      if ((tmp =
		   nxmle_find_attribute (cur,
					 TB_CONTROLLER_COMMAND_INLINE_ATTR,
					 NULL)))
		{
		  if (!strcmp (tmp, "true"))
		    process->command_inline = TRUE;

		  free (tmp);
		}
	    }

	  else if (!strcmp (cur->value, TB_CONTROLLER_INPUT_TAG))
	    {
	      if (nxml_get_string (cur, &tmp) == NXML_OK && tmp)
		{
		  if (!
		      (process->input =
		       io_string_new (tmp, -1, &data->swap, error)))
		    {
		      free (tmp);
		      return FALSE;
		    }

		  free (tmp);
		}

	      if ((tmp =
		   nxmle_find_attribute (cur, TB_CONTROLLER_INPUT_TRIM_ATTR,
					 NULL)))
		{
		  if (!strcmp (tmp, "true"))
		    process->input_trim = TRUE;

		  free (tmp);
		}

	      if ((tmp =
		   nxmle_find_attribute (cur, TB_CONTROLLER_INPUT_FP_ATTR,
					 NULL)))
		{
		  if (!strcmp (tmp, "true"))
		    process->input_from_parent = TRUE;

		  free (tmp);
		}
	    }

	  else if (!strcmp (cur->value, TB_CONTROLLER_SCHEDULED_TAG))
	    {
	      for (attribute = cur->attributes; attribute;
		   attribute = attribute->next)
		{
		  if (!strcmp (attribute->name, TB_CONTROLLER_TIME_ATTR))
		    {
		      if (process_parser_scheduled
			  (process, attribute->value, error) == FALSE)
			return FALSE;
		    }

		  else if (!strcmp (attribute->name, TB_CONTROLLER_WFI_ATTR))
		    process->wait_for_istances = TRUE;
		}
	    }

	  else if (!strcmp (cur->value, TB_CONTROLLER_PROCESSES_TAG))
	    {
	      nxml_data_t *ncur;

	      for (ncur = cur->children; ncur; ncur = ncur->next)
		{
		  if (ncur->type == NXML_TYPE_ELEMENT
		      && (!strcmp (ncur->value, TB_CONTROLLER_PROCESS_TAG)
			  || !strcmp (ncur->value,
				      TB_CONTROLLER_METAPROCESS_TAG))
		      && process_parser_single (data, nxml, ncur, list,
						process, is_ref,
						error) == FALSE)
		    return FALSE;
		}
	    }
	}
    }

  if (process->scheduled && process->global == FALSE)
    {
      g_set_error (error, controller_error_quark (), 0,
		   "The process '%s' is not global but contains the scheduled tag",
		   process->id);
      return FALSE;
    }

  if (!process->command)
    {
      g_set_error (error, controller_error_quark (), 0,
		   "The process '%s' doesn't have a command", process->id);
      return FALSE;
    }

  if (process->meta && process->processes)
    {
      g_set_error (error, controller_error_quark (), 0,
		   "The process '%s' is a meta process but has sub processes",
		   process->id);
      return FALSE;
    }

  process->processes =
    g_list_sort (process->processes, (GCompareFunc) process_parser_order);

  return TRUE;
}

static gboolean
process_parser_scheduled (TBProcess * process, gchar * string,
			  GError ** error)
{
  gchar *ptr = string;
  guint total = 0;
  gboolean h, m, s;
  guint hv, mv, sv;

  h = m = s = FALSE;
  hv = mv = sv = 0;

  while (ptr && *ptr)
    {
      if (*ptr >= '0' && *ptr <= '9')
	total = total * 10 + *ptr - '0';

      else
	switch (*ptr)
	  {
	  case 'h':
	  case 'H':
	    if (h == TRUE)
	      {
		g_set_error (error, controller_error_quark (), 0,
			     "The scheduled string '%s' contains error chars.",
			     string);
		return FALSE;
	      }

	    hv = total;
	    total = 0;
	    h = TRUE;
	    break;

	  case 'm':
	  case 'M':
	    if (m == TRUE)
	      {
		g_set_error (error, controller_error_quark (), 0,
			     "The scheduled string '%s' contains error chars.",
			     string);
		return FALSE;
	      }

	    mv = total;
	    total = 0;
	    m = TRUE;
	    break;

	  case 's':
	  case 'S':
	    if (s == TRUE)
	      {
		g_set_error (error, controller_error_quark (), 0,
			     "The scheduled string '%s' contains error chars.",
			     string);
		return FALSE;
	      }

	    sv = total;
	    total = 0;
	    s = TRUE;
	    break;

	  default:
	    g_set_error (error, controller_error_quark (), 0,
			 "The scheduled string '%s' contains error chars.",
			 string);
	    return FALSE;
	  }

      ptr++;
    }

  if (total && s == FALSE)
    sv = total;

  if (hv || mv || sv)
    process->scheduled = hv * 3600 + mv * 60 + sv;

  return TRUE;
}

static gint
process_parser_order (TBSubProcess * a, TBSubProcess * b)
{
  if (a->order < b->order)
    return -1;

  if (b->order < a->order)
    return 1;

  return 0;
}

/* FREE ********************************************************************/
static void process_sub_free (TBSubProcess * data);

void
process_free (TBProcess * data)
{
  if (!data)
    return;

  if (data->id)
    g_free (data->id);

  if (data->command)
    g_free (data->command);

  if (data->input)
    io_string_unref (data->input);

  if (data->processes)
    {
      g_list_foreach (data->processes, (GFunc) process_sub_free, NULL);
      g_list_free (data->processes);
    }

  g_free (data);
}

static void
process_sub_free (TBSubProcess * data)
{
  if (!data)
    return;

  if (data->process)
    g_free (data->process);

  g_free (data);
}

void
process_ref (TBProcess * process)
{
  if (!process || process->is_ref == FALSE)
    return;

  process->ref++;
}

void
process_unref (TBProcess * process)
{
  if (!process || process->is_ref == FALSE)
    return;

  process->ref--;

  if (process->ref <= 0)
    process_free (process);
}

/* TIME CHECK **************************************************************/

/* This function returns TRUE if the current process is to schedule */
gboolean
process_is_to_schedule (TBGlobal * data, TBProcess * process)
{
  GTimeVal ctime;

  /* Current time: */
  g_source_get_current_time (data->loop_source, &ctime);

  /* If the process is not global, skip: */
  if (process->global == FALSE || !process->scheduled)
    return FALSE;

  /* If it the first time, do it: */
  if (process->last_scheduled == 0)
    return TRUE;

  /* If the current time is not good enough */
  if (process->scheduled >= ctime.tv_sec - process->last_scheduled)
    return FALSE;

  /* If we have not to wait the previous istances, do it: */
  if (process->wait_for_istances == FALSE)
    return TRUE;

  /* Search if something is running for this project: */
  if (process->istances_running || process->istances_wait_numb)
    return FALSE;

  /* Schedule it: */
  return TRUE;
}

/* EOF */
